/***************************************************************************************
 *  Nonstandard serial attachment for remote 2741 terminal (IO selectric) used by APL\1130
 *  This implementation may be incomplete and/or incorrect
 ***************************************************************************************/

#include "ibm1130_defs.h"
#include "sim_sock.h"
#include "sim_tmxr.h"

#define DEBUG_T2741

static TMLN t2741_ldsc = { 0 };							/* line descr for telnet attachment */
static TMXR t2741_tmxr = { 1, 0, 0, &t2741_ldsc };		/* line mux for telnet attachment */

#define T2741_DSW_TRANSMIT_NOT_READY			0x4000
#define T2741_DSW_READ_RESPONSE					0x1000
#define T2741_DSW_READ_OVERRUN					0x0800
#define T2741_DSW_ATTENTION						0x0010

#define IS_ONLINE(u) (((u)->flags & (UNIT_ATT|UNIT_DIS)) == UNIT_ATT)

#define UNIT_V_PHYSICAL_TERM (UNIT_V_UF + 0)				/* indicates not telnet but attachment to real terminal */
#define UNIT_V_UPCASE		 (UNIT_V_UF + 1)				/* indicates upshift performed */
#define UNIT_V_SENDING		 (UNIT_V_UF + 2)				/* indicates not telnet but attachment to real terminal */
#define UNIT_V_RECEIVING	 (UNIT_V_UF + 3)				/* indicates not telnet but attachment to real terminal */

#define UNIT_PHYSICAL_TERM	 (1u << UNIT_V_PHYSICAL_TERM)
#define UNIT_UPCASE			 (1u << UNIT_V_UPCASE)
#define UNIT_SENDING		 (1u << UNIT_V_SENDING)
#define UNIT_RECEIVING	 	 (1u << UNIT_V_RECEIVING)

#define CODE_SHIFTUP		0x1C00
#define CODE_SHIFTDOWN		0x7C00
#define CODE_CIRCLEC		0x1F00
#define CODE_CIRCLED		0x1600
#define CODE_RETURN			0x5B00
#define CODE_LINEFEED		0x3B00	
#define CODE_ATTENTION		0x0001			/* pseudocode, never really returned as a received character */
#define CODE_UNKNOWN		0x0000

static t_stat t2741_svc      (UNIT *uptr);
static t_stat t2741_reset    (DEVICE *dptr);
static t_stat t2741_attach   (UNIT *uptr, CONST char *cptr);
static t_stat t2741_detach   (UNIT *uptr);
static uint16 ascii_to_t2741 (int ascii);
static const char * t2741_to_ascii (uint16 code);
static void set_transmit_notready (void);

static uint16 t2741_dsw    = T2741_DSW_TRANSMIT_NOT_READY;	/* device status word      */
static uint32 t2741_swait  = 200;							/* character send wait     */
static uint32 t2741_rwait  = 2000;							/* character receive wait  */
static uint16 t2741_char   = 0;								/* last character received */
static int    overrun	   = FALSE;
static uint32 t2741_socket = 1130;

UNIT t2741_unit[1] = {
	{ UDATA (&t2741_svc, UNIT_ATTABLE, 0) },
};

REG t2741_reg[] = {
	{ HRDATA (DSW, 	    t2741_dsw,  16) },				/* device status word */
	{ DRDATA (RTIME,    t2741_rwait, 24), PV_LEFT },	/* character receive wait */
	{ DRDATA (STIME,    t2741_swait, 24), PV_LEFT },	/* character send wait */
	{ DRDATA (SOCKET,   t2741_socket,16), PV_LEFT },	/* socket number */
	{ HRDATA (LASTCHAR, t2741_char,  16), PV_LEFT },	/* last character read */
	{ NULL }  };

DEVICE t2741_dev = {
	"T2741", t2741_unit, t2741_reg, NULL,
	1, 16, 16, 1, 16, 16,
	NULL, NULL, t2741_reset,
	NULL, t2741_attach, t2741_detach};

/* xio_t2741_terminal - XIO command interpreter for the terminal adapter */

void xio_t2741_terminal (int32 iocc_addr, int32 iocc_func, int32 iocc_mod)
{
	char msg[80];
	uint16 code;

	switch (iocc_func) {
		case XIO_READ:											/* read: return last character read */
			code = t2741_char & 0xFF00;
			M[iocc_addr & mem_mask] = code;
			overrun = FALSE;
#ifdef DEBUG_T2741
/*			trace_both("T2741 %04x READ %02x %s", prev_IAR, code >> 8, t2741_to_ascii(code)); */
#endif
			break;

		case XIO_WRITE:											/* write: initiate character send */
			code = M[iocc_addr & mem_mask] & 0xFF00;
#ifdef DEBUG_T2741
			trace_both("T2741 %04x SEND %02x %s", prev_IAR, code >> 8, t2741_to_ascii(code));
#endif
			SETBIT(t2741_dsw, T2741_DSW_TRANSMIT_NOT_READY);
			SETBIT(t2741_unit->flags, UNIT_SENDING);

			if (code == CODE_SHIFTUP)
				SETBIT(t2741_unit->flags, UNIT_UPCASE);
			else if (code == CODE_SHIFTDOWN)
				CLRBIT(t2741_unit->flags, UNIT_UPCASE);

			sim_activate(t2741_unit, t2741_swait);				/* schedule interrupt */
			break;

		case XIO_SENSE_DEV:										/* sense device status */
			ACC = t2741_dsw;
#ifdef DEBUG_T2741
/*			trace_both("T2741 %04x SENS %04x%s", prev_IAR, t2741_dsw, (iocc_mod & 0x01) ? " reset" : ""); */
#endif
			if (iocc_mod & 0x01) {								/* reset interrupts */
				CLRBIT(t2741_dsw, T2741_DSW_READ_RESPONSE);
				CLRBIT(ILSW[4], ILSW_4_T2741_TERMINAL);
			}
			break;

		case XIO_CONTROL:										/* control: do something to interface */
#ifdef DEBUG_T2741
			trace_both("T2741 %04x CTRL %04x", prev_IAR, iocc_mod &0xFF);
#endif
			SETBIT(t2741_unit->flags, UNIT_RECEIVING);			/* set mode to receive mode */
			if (IS_ONLINE(t2741_unit) && (t2741_char != 0 || ! feof(t2741_unit->fileref))) {
				sim_activate(t2741_unit, t2741_rwait);
				t2741_char = (CODE_CIRCLED >> 8);				/* first character received after turnaround is circled */
			}
			break;

		default:
			sprintf(msg, "Invalid T2741 XIO function %x", iocc_func);
			xio_error(msg);
	}
}

static void set_transmit_notready (void)
{
	if (IS_ONLINE(t2741_unit) && ! (t2741_unit->flags & UNIT_SENDING))
		CLRBIT(t2741_dsw, T2741_DSW_TRANSMIT_NOT_READY);
	else
		SETBIT(t2741_dsw, T2741_DSW_TRANSMIT_NOT_READY);
}

static t_stat t2741_svc (UNIT *uptr)
{
	int ch = EOF;
	uint16 code;

	if (uptr->flags & UNIT_SENDING) {						/* xmit: no interrupt, as far as I know. just clr busy bit */
		CLRBIT(uptr->flags, UNIT_SENDING);
		set_transmit_notready();
	}

	if (uptr->flags & UNIT_RECEIVING) {						/* rcv: fire interrupt */
		t2741_char <<= 8;

		if (t2741_char == 0) {								/* there is no 2nd character from previous ascii input */
			if ((ch = getc(t2741_unit->fileref)) == EOF)
				t2741_char = 0;
			else {
				if (ch == '\r') {								/* if we get CR, jump to LF */
					if ((ch = getc(t2741_unit->fileref)) != '\n') {
						ungetc(ch, t2741_unit->fileref);
						ch = '\r';
					}
				}

				if (ch == '\027') {
					t2741_char = CODE_LINEFEED;					/* attention key sends line feed character */
#ifdef DEBUG_T2741
					trace_both("T2741 ---- ATTENTION");
#endif
					SETBIT(t2741_dsw, T2741_DSW_ATTENTION);		/* no character returned ? */
				}
				else {
					t2741_char = ascii_to_t2741(ch);			/* translate to 2741 code(s) */
				}
			}
		}

		code = t2741_char & 0xFF00;

		if (t2741_char != 0) {
			if (overrun)										/* previous character was not picked up! */
				SETBIT(t2741_dsw, T2741_DSW_READ_OVERRUN);

			SETBIT(t2741_dsw, T2741_DSW_READ_RESPONSE);
			SETBIT(ILSW[4], ILSW_4_T2741_TERMINAL);				/* issue interrupt */
			calc_ints();

#ifdef DEBUG_T2741
			trace_both("T2741 ---- RCVD %02x '%s' RDRESP%s%s", code >> 8, t2741_to_ascii(code),
				(t2741_dsw & T2741_DSW_READ_OVERRUN) ? "|OVERRUN" : "",
				(t2741_dsw & T2741_DSW_ATTENTION)    ? "|ATTENTION" : "");
#endif

			overrun = TRUE;										/* arm overrun flag */
		}

		if (t2741_char == CODE_CIRCLEC)							/* end of line (CIRCLEC after RETURN) auto downshifts */
			CLRBIT(t2741_unit->flags, UNIT_UPCASE);

		if (t2741_char == 0 || code == CODE_CIRCLEC)
			CLRBIT(uptr->flags, UNIT_RECEIVING);				/* on enter or EOF, stop typing */
		else
			sim_activate(t2741_unit, t2741_rwait);				/* schedule next character to arrive */
	}

	return SCPE_OK;
}

static t_stat t2741_attach (UNIT *uptr, CONST char *cptr)
{
	int rval;

	if ((rval = attach_unit(uptr, cptr)) == SCPE_OK) {			/* use standard attach */
		t2741_char = 0;
		overrun    = FALSE;

		CLRBIT(t2741_unit->flags, UNIT_UPCASE);

		if ((t2741_unit->flags & UNIT_RECEIVING) && ! feof(t2741_unit->fileref))
			sim_activate(t2741_unit, t2741_rwait);					/* schedule interrupt */
	}

	set_transmit_notready();

	return rval;
}

static t_stat t2741_detach (UNIT *uptr)
{
	t_stat rval;

	if (t2741_unit->flags & UNIT_RECEIVING)					/* if receive was pending, cancel interrupt */
		sim_cancel(t2741_unit);

	t2741_char = 0;
	overrun    = FALSE;

	rval = detach_unit(uptr);								/* use standard detach */

	set_transmit_notready();

	return rval;
}

static t_stat t2741_reset  (DEVICE *dptr)
{
	sim_cancel(t2741_unit);

	CLRBIT(t2741_unit->flags, UNIT_SENDING|UNIT_RECEIVING|UNIT_UPCASE);

	t2741_char = 0;
	t2741_dsw  = 0;
	overrun    = FALSE;

	set_transmit_notready();

	CLRBIT(ILSW[4], ILSW_4_T2741_TERMINAL);
	calc_ints();

	return SCPE_OK;
}

static struct tag_t2741_map {
	int code;
	int lcase, ucase;
	t_bool shifts;
} t2741_map[] = {
	{0x4F00, 'A', 'a', TRUE},
	{0x3700, 'B', 'b', TRUE},
	{0x2F00, 'C', 'c', TRUE},
	{0x2A00, 'D', 'd', TRUE},
	{0x2900, 'E', 'e', TRUE},
	{0x6700, 'F', '_', TRUE},
	{0x6200, 'G', 'g', TRUE},
	{0x3200, 'H', 'h', TRUE},
	{0x4C00, 'I', 'i', TRUE},
	{0x6100, 'J', 'j', TRUE},
	{0x2C00, 'K', '\'', TRUE},
	{0x3100, 'L', 'l', TRUE},
	{0x4300, 'M', '|', TRUE},
	{0x2500, 'N', 'n', TRUE},
	{0x5100, 'O', 'o', TRUE},
	{0x6800, 'P', '*', TRUE},
	{0x6D00, 'Q', '?', TRUE},
	{0x4A00, 'R', 'r', TRUE},
	{0x5200, 'S', 's', TRUE},
	{0x2000, 'T', '~', TRUE},
	{0x2600, 'U', 'u', TRUE},
	{0x4600, 'V', 'v', TRUE},
	{0x5700, 'W', 'w', TRUE},
	{0x2300, 'X', 'x', TRUE},
	{0x7300, 'Y', 'y', TRUE},
	{0x1500, 'Z', 'z', TRUE},
	{0x1300, '0', '&', TRUE},
	{0x0200, '1', '?', TRUE},
	{0x0400, '2', '?', TRUE},
	{0x0700, '3', '<', TRUE},
	{0x1000, '4', '?', TRUE},
	{0x0800, '5', '=', TRUE},
	{0x0D00, '6', '?', TRUE},
	{0x0B00, '7', '>', TRUE},
	{0x0E00, '8', '?', TRUE},
	{0x1600, '9', '|', TRUE},
	{0x7000, '/', '\\', TRUE},
	{0x7600, '+', '-', TRUE},
	{0x6400, '?', '?', TRUE},
	{0x4000, '<', '>', TRUE},
	{0x6B00, '[', '(', TRUE},
	{0x4900, ']', ')', TRUE},
	{0x6E00, ',', ';', TRUE},
	{0x4500, '.', ':', TRUE},
	{0x0100, ' ',  0,  FALSE},
	{0x5B00, '\r', 0,  FALSE},
	{0x3B00, '\n', 0,  FALSE},
	{0x5D00, '\b', 0,  FALSE},
	{0x5E00, '\t', 0,  FALSE},
	{0x0001, '\027', 0,  FALSE},
};

static uint16 ascii_to_t2741 (int ascii)
{
	int i;
	uint16 rval = 0;

	ascii &= 0xFF;

	if (ascii == '\n')			/* turn newlines into returns + CIRCLED? */
		return CODE_RETURN | (CODE_CIRCLEC >> 8);

	for (i = sizeof(t2741_map)/sizeof(t2741_map[0]); --i >= 0; ) {
		if (t2741_map[i].shifts) {
			if (t2741_map[i].lcase == ascii) {
				rval = t2741_map[i].code;
				if (t2741_unit->flags & UNIT_UPCASE) {
					CLRBIT(t2741_unit->flags, UNIT_UPCASE);
					rval = CODE_SHIFTDOWN | (rval >> 8);
				}
				return rval;
			}
			if (t2741_map[i].ucase == ascii) {
				rval = t2741_map[i].code;
				if (! (t2741_unit->flags & UNIT_UPCASE)) {
					SETBIT(t2741_unit->flags, UNIT_UPCASE);
					rval = CODE_SHIFTUP | (rval >> 8);
				}
				return rval;
			}
		}
		else if (t2741_map[i].lcase == ascii)
			return t2741_map[i].code;
	}

	return CODE_UNKNOWN;
}

static const char * t2741_to_ascii (uint16 code)
{
	int i;
	static char string[2] = {'?', '\0'};

	switch (code) {
		case CODE_SHIFTUP:		return "SHIFTUP";
		case CODE_SHIFTDOWN:	return "SHIFTDN";
		case CODE_CIRCLEC:		return "CIRCLEC";
		case CODE_CIRCLED:		return "CIRCLED";
	}

	for (i = sizeof(t2741_map)/sizeof(t2741_map[0]); --i >= 0; ) {
		if (t2741_map[i].code == code) {
			if (t2741_map[i].shifts) {
				string[0] = (t2741_unit->flags & UNIT_UPCASE) ? t2741_map[i].ucase : t2741_map[i].lcase;
				return string;
			}
			switch (t2741_map[i].lcase) {
				case ' ':	return " ";
				case '\r':	return "RETURN";
				case '\n':	return "LINEFEED";
				case '\b':	return "BS";
				case '\t':	return "IDLE";
			}
			break;
		}
	}

	return "?";
}
